package com.sangmo.fts.config;

import com.sangmo.boot.framework.common.errors.ServiceException;
import com.sangmo.boot.framework.common.utils.JsonUtils;
import com.sangmo.fts.basic.model.AccessLog;
import com.sangmo.fts.basic.service.AccessLogEntityService;
import com.sangmo.fts.common.TransactionTrace;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashMap;
import java.util.Map;

/**
 * @DateTime 2020/7/22 17:50
 * @Description 拦截API接口，记录日志，包括参数，返回值，请求时长，状态
 */
@Component
@Aspect
public class AccessLogAspect {

    private static Logger LOGGER = LoggerFactory.getLogger(AccessLogAspect.class);

    @Autowired
    private AccessLogEntityService accessLogEntityService;

    @Pointcut("execution(* com.sangmo..api..*Api.*(..))")
    private void executer(){}

    @Around(value = "executer()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        boolean isMeta = null != request.getParameter("_meta");
        boolean isDocs = null != request.getParameter("_docs");
        if (isMeta || isDocs) {
            return point.proceed();
        }

        long beforeMilliSecond = System.currentTimeMillis();
        String status = AccessLog.States.SUCCESS;
        String message = null;
        long duration = 0;
        Object ret = null;
        try {
            ret = point.proceed();
            return ret;
        } catch (Throwable e) {
            status = AccessLog.States.FAIL;
            message = e.getMessage();
            if (!(e instanceof ServiceException)) {
                PrintWriter pw = null;
                try
                {
                    StringWriter sw = new StringWriter();
                    pw = new PrintWriter(sw);
                    e.printStackTrace(pw);
                    message = sw.toString();
                } catch (Exception ex) {
                } finally {
                    IOUtils.closeQuietly(pw);
                }
            }
            throw e;
        } finally {
            duration = System.currentTimeMillis() - beforeMilliSecond;
            this.log(point, ret, status, message, duration);
        }
    }

    private void log(ProceedingJoinPoint point, Object returnObj, String status, String message, long duration){
        AccessLog log = null;
        try {
            Object[] args = point.getArgs();
            Method method = ((MethodSignature)point.getSignature()).getMethod();
            HttpServletRequest request=((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();

            String urlParams = request.getQueryString();
            String requestUrl = request.getRequestURI();
            if (StringUtils.isNotEmpty(urlParams)) {
                requestUrl = requestUrl + "?" + urlParams;
            }
            Parameter[] parameters = method.getParameters();
            Map<String,Object> paramsMap = new HashMap<>();
            for ( int i = 0 ; i < parameters.length ; i ++){
                Parameter parameter = parameters[i];
                if(args[i] instanceof Serializable && !(args[i] instanceof MultipartFile)){
                    paramsMap.put(parameter.getName(), args[i]);
                }
            }

            log = new AccessLog();
            log.setUrl(requestUrl);

            log.setRequest(StringUtils.left(JsonUtils.writeValue(paramsMap), 2000));
            if (null != returnObj) {
                log.setResponse(StringUtils.left(JsonUtils.writeValue(returnObj), 2000));
            }
            log.setClientIp(this.getIPAddress(request));
            log.setMessage(StringUtils.left(message, 2000));
            log.setState(status);
            log.setDuration(duration);
            log.setTraceId(TransactionTrace.getId());
            accessLogEntityService.save(log);
        } catch (Exception ex) {
            if (null != log) {
                LOGGER.info(JsonUtils.writeValue(log));
            }
            LOGGER.error("Error in Access Logging", ex);
        }
    }

    private String getIPAddress(HttpServletRequest request){
        String ip = null;
        String ipAddresses = request.getHeader("X-Forwarded-For");
        if (StringUtils.isEmpty(ipAddresses) || "unknown".equalsIgnoreCase(ipAddresses)) {
            ipAddresses = request.getHeader("Proxy-Client-IP");
        }
        if (StringUtils.isEmpty(ipAddresses) || "unknown".equalsIgnoreCase(ipAddresses)) {
            ipAddresses = request.getHeader("WL-Proxy-Client-IP");
        }
        if (StringUtils.isEmpty(ipAddresses) || "unknown".equalsIgnoreCase(ipAddresses)) {
            ipAddresses = request.getHeader("HTTP_CLIENT_IP");
        }
        if (StringUtils.isEmpty(ipAddresses) || "unknown".equalsIgnoreCase(ipAddresses)) {
            ipAddresses = request.getHeader("X-Real-IP");
        }
        if (StringUtils.isNotEmpty(ipAddresses)) {
            ip = ipAddresses.split(",")[0];
        }
        if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ipAddresses)) {
            ip = request.getRemoteAddr();
        }
        return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
    }
}
